package com.zj.service;

import java.util.concurrent.ConcurrentHashMap;

import org.springframework.stereotype.Service;

import com.zj.common.ClientType;
import com.zj.dto.CameraDto;
import com.zj.thread.MediaTransfer;
import com.zj.thread.MediaTransferFlvByFFmpeg;
import com.zj.thread.MediaTransferFlvByJavacv;

import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.crypto.digest.MD5;
import io.netty.channel.ChannelHandlerContext;

/**
 * 媒体服务，支持全局网络超时、读写超时、无人拉流持续时长自动关闭流等配置
 *
 * @author ZJ
 * @author eguid
 */
@Service
public class MediaService {

    /**
     * 缓存流转换线程
     */
    public static ConcurrentHashMap<String, MediaTransfer> cameras = new ConcurrentHashMap<>();


    /**
     * http-flv播放
     * @param cameraDto
     * @param ctx
     */
    /**
     * 根据不同的媒体转换策略，为相机流添加HTTP播放客户端。
     *
     * @param cameraDto 相机配置信息，包含媒体键和是否启用FFmpeg转换。
     * @param ctx       通道处理上下文，用于添加HTTP播放的客户端。
     */
    public void playForHttp(CameraDto cameraDto, ChannelHandlerContext ctx) {
        // 检查是否已经存在对应的媒体转换实例
        if (cameras.containsKey(cameraDto.getMediaKey())) {
            MediaTransfer mediaConvert = cameras.get(cameraDto.getMediaKey());
            // 如果当前媒体转换是基于JavaCV的FLV转换
            if (mediaConvert instanceof MediaTransferFlvByJavacv) {
                MediaTransferFlvByJavacv mediaTransferFlvByJavacv = (MediaTransferFlvByJavacv) mediaConvert;
                // 如果启用了FFmpeg转换，则停止当前的JavaCV转换，重新启动FFmpeg转换
                // 如果当前已经用ffmpeg，则重新拉流
                if (cameraDto.isEnabledFFmpeg()) {
                    mediaTransferFlvByJavacv.setRunning(false);
                    cameras.remove(cameraDto.getMediaKey());
                    this.playForHttp(cameraDto, ctx);
                } else {
                    // 如果未启用FFmpeg转换，仅添加HTTP客户端
                    mediaTransferFlvByJavacv.addClient(ctx, ClientType.HTTP);
                }
            } else if (mediaConvert instanceof MediaTransferFlvByFFmpeg) {
                MediaTransferFlvByFFmpeg mediaTransferFlvByFFmpeg = (MediaTransferFlvByFFmpeg) mediaConvert;
                // 如果当前媒体转换是基于FFmpeg的FLV转换
                // 如果当前已经用javacv，则关闭再重新拉流
                if (!cameraDto.isEnabledFFmpeg()) {
                    mediaTransferFlvByFFmpeg.stopFFmpeg();
                    cameras.remove(cameraDto.getMediaKey());
                    this.playForHttp(cameraDto, ctx);
                } else {
                    // 如果启用了FFmpeg转换，仅添加HTTP客户端
                    mediaTransferFlvByFFmpeg.addClient(ctx, ClientType.HTTP);
                }
            }
        } else {
            // 如果不存在对应的媒体转换实例
            // 根据是否启用了FFmpeg转换，创建并启动相应的媒体转换实例
            if (cameraDto.isEnabledFFmpeg()) {
                MediaTransferFlvByFFmpeg mediaft = new MediaTransferFlvByFFmpeg(cameraDto);
                mediaft.execute();
                cameras.put(cameraDto.getMediaKey(), mediaft);
                mediaft.addClient(ctx, ClientType.HTTP);
            } else {
                MediaTransferFlvByJavacv mediaConvert = new MediaTransferFlvByJavacv(cameraDto);
                cameras.put(cameraDto.getMediaKey(), mediaConvert);
                ThreadUtil.execute(mediaConvert);
                mediaConvert.addClient(ctx, ClientType.HTTP);
            }
        }
    }

    /**
     * ws-flv播放
     * @param cameraDto
     * @param ctx
     */
    /**
     * 根据传入的摄像头信息和通道上下文，播放摄像头流。
     * 如果已经存在对应的媒体转换实例，则根据是否启用ffmpeg来决定是重新拉流还是添加新客户端。
     * 如果不存在对应的媒体转换实例，则根据是否启用ffmpeg来创建并启动相应的媒体转换实例，并添加新客户端。
     *
     * @param cameraDto 摄像头信息数据传输对象，包含媒体键和是否启用ffmpeg等信息。
     * @param ctx       通道处理上下文，用于添加客户端。
     */
    public void playForWs(CameraDto cameraDto, ChannelHandlerContext ctx) {
        // 检查是否已经存在对应的媒体转换实例
        if (cameras.containsKey(cameraDto.getMediaKey())) {
            MediaTransfer mediaConvert = cameras.get(cameraDto.getMediaKey());
            // 如果是javacv实现的媒体转换
            if (mediaConvert instanceof MediaTransferFlvByJavacv) {
                MediaTransferFlvByJavacv mediaTransferFlvByJavacv = (MediaTransferFlvByJavacv) mediaConvert;
                // 如果当前启用ffmpeg，停止javacv拉流，移除旧实例，重新调用自身启动ffmpeg拉流
                // 如果当前已经用ffmpeg，则重新拉流
                if (cameraDto.isEnabledFFmpeg()) {
                    mediaTransferFlvByJavacv.setRunning(false);
                    cameras.remove(cameraDto.getMediaKey());
                    this.playForWs(cameraDto, ctx);
                } else {
                    // 如果当前不启用ffmpeg，添加新客户端
                    mediaTransferFlvByJavacv.addClient(ctx, ClientType.WEBSOCKET);
                }
            } else if (mediaConvert instanceof MediaTransferFlvByFFmpeg) {
                MediaTransferFlvByFFmpeg mediaTransferFlvByFFmpeg = (MediaTransferFlvByFFmpeg) mediaConvert;
                // 如果当前不启用ffmpeg，停止ffmpeg拉流，移除旧实例，重新调用自身启动javacv拉流
                // 如果当前已经用javacv，则关闭再重新拉流
                if (!cameraDto.isEnabledFFmpeg()) {
                    mediaTransferFlvByFFmpeg.stopFFmpeg();
                    cameras.remove(cameraDto.getMediaKey());
                    this.playForWs(cameraDto, ctx);
                } else {
                    // 如果当前启用ffmpeg，添加新客户端
                    mediaTransferFlvByFFmpeg.addClient(ctx, ClientType.WEBSOCKET);
                }
            }
        } else {
            // 如果不存在对应的媒体转换实例，根据是否启用ffmpeg创建并启动相应的媒体转换实例
            if (cameraDto.isEnabledFFmpeg()) {
                MediaTransferFlvByFFmpeg mediaft = new MediaTransferFlvByFFmpeg(cameraDto);
                mediaft.execute();
                cameras.put(cameraDto.getMediaKey(), mediaft);
                mediaft.addClient(ctx, ClientType.WEBSOCKET);
            } else {
                MediaTransferFlvByJavacv mediaConvert = new MediaTransferFlvByJavacv(cameraDto);
                cameras.put(cameraDto.getMediaKey(), mediaConvert);
                ThreadUtil.execute(mediaConvert);
                mediaConvert.addClient(ctx, ClientType.WEBSOCKET);
            }
        }
    }

    /**
     * api播放
     * @param cameraDto
     * @return
     */
    /**
     * 根据提供的相机信息尝试播放视频流。
     *
     * @param cameraDto 相机的数据传输对象，包含相机的URL和其他必要信息。
     * @return 如果成功启动媒体传输并开始播放，则返回true；否则返回false。
     */
    public boolean playForApi(CameraDto cameraDto) {
        // 通过URL生成媒体键，用于唯一标识媒体传输任务。
        // 区分不同媒体
        String mediaKey = MD5.create().digestHex(cameraDto.getUrl());
        cameraDto.setMediaKey(mediaKey);
        cameraDto.setEnabledFlv(true);

        // 尝试从相机映射中获取现有的媒体传输实例。
        MediaTransfer mediaTransfer = cameras.get(cameraDto.getMediaKey());
        // 如果当前没有对应的媒体传输实例，
        if (null == mediaTransfer) {
            // 如果启用了FFmpeg，则使用FFmpeg进行媒体传输。
            if (cameraDto.isEnabledFFmpeg()) {
                MediaTransferFlvByFFmpeg mediaft = new MediaTransferFlvByFFmpeg(cameraDto);
                mediaft.execute();
                cameras.put(cameraDto.getMediaKey(), mediaft);
            } else {
                // 否则，使用JavaCV进行媒体传输。
                MediaTransferFlvByJavacv mediaConvert = new MediaTransferFlvByJavacv(cameraDto);
                cameras.put(cameraDto.getMediaKey(), mediaConvert);
                ThreadUtil.execute(mediaConvert);
            }
        }

        // 再次获取媒体传输实例，以确保其已正确创建和存储。
        mediaTransfer = cameras.get(cameraDto.getMediaKey());
        //同步等待
        // 根据媒体传输的类型，检查媒体传输是否已成功启动并准备就绪。
        if (mediaTransfer instanceof MediaTransferFlvByJavacv) {
            MediaTransferFlvByJavacv mediaft = (MediaTransferFlvByJavacv) mediaTransfer;
            // 30秒还没true认为启动不了
            for (int i = 0; i < 60; i++) {
                // 检查媒体传输是否正在运行，以及捕获和记录器状态是否正常。
                if (mediaft.isRunning() && mediaft.isGrabberStatus() && mediaft.isRecorderStatus()) {
                    return true;
                }
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                }
            }
        } else if (mediaTransfer instanceof MediaTransferFlvByFFmpeg) {
            MediaTransferFlvByFFmpeg mediaft = (MediaTransferFlvByFFmpeg) mediaTransfer;
            // 30秒还没true认为启动不了
            for (int i = 0; i < 60; i++) {
                // 检查FFmpeg媒体传输是否正在运行。
                if (mediaft.isRunning()) {
                    return true;
                }
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                }
            }
        }
        // 如果在指定时间内媒体传输未能成功启动，则返回false。
        return false;
    }

    /**
     * 关闭流
     * @param cameraDto
     */
    /**
     * 关闭指定摄像头的API流。
     *
     * @param cameraDto 摄像头信息的数据传输对象，包含媒体键和是否启用FLV流的标志。
     *                  通过这个对象，我们可以识别要操作的摄像头并关闭其FLV流。
     */
    public void closeForApi(CameraDto cameraDto) {
        // 禁用FLV流
        cameraDto.setEnabledFlv(false);

        // 检查摄像头是否存在于cameras映射中
        if (cameras.containsKey(cameraDto.getMediaKey())) {
            MediaTransfer mediaConvert = cameras.get(cameraDto.getMediaKey());

            // 根据媒体转换器的类型，采取不同的关闭策略
            if (mediaConvert instanceof MediaTransferFlvByJavacv) {
                MediaTransferFlvByJavacv mediaTransferFlvByJavacv = (MediaTransferFlvByJavacv) mediaConvert;
                // 关闭JavaCV处理的FLV流
                mediaTransferFlvByJavacv.setRunning(false);
                // 从映射中移除摄像头
                cameras.remove(cameraDto.getMediaKey());
            } else if (mediaConvert instanceof MediaTransferFlvByFFmpeg) {
                MediaTransferFlvByFFmpeg mediaTransferFlvByFFmpeg = (MediaTransferFlvByFFmpeg) mediaConvert;
                // 停止FFmpeg处理的FLV流
                mediaTransferFlvByFFmpeg.stopFFmpeg();
                // 从映射中移除摄像头
                cameras.remove(cameraDto.getMediaKey());
            }
        }
    }

}
